﻿using System;
using GeoAPI.Geometries;

namespace YJKGridTopologyCal.Algorithm.Distance
{
	public class DiscreteHausdorffDistance
	{
		public static double Distance(IGeometry g0, IGeometry g1)
		{
			return new DiscreteHausdorffDistance(g0, g1).Distance();
		}

		public static double Distance(IGeometry g0, IGeometry g1, double densifyFraction)
		{
			return new DiscreteHausdorffDistance(g0, g1)
			{
				DensifyFraction = densifyFraction
			}.Distance();
		}

		public DiscreteHausdorffDistance(IGeometry g0, IGeometry g1)
		{
			this._g0 = g0;
			this._g1 = g1;
		}

		public double DensifyFraction
		{
			get
			{
				return this._densifyFrac;
			}
			set
			{
				if (value > 1.0 || value <= 0.0)
				{
					throw new ArgumentOutOfRangeException("value", "Fraction is not in range (0.0 - 1.0]");
				}
				this._densifyFrac = value;
			}
		}

		public double Distance()
		{
			this.Compute(this._g0, this._g1);
			return this._ptDist.Distance;
		}

		public double OrientedDistance()
		{
			this.ComputeOrientedDistance(this._g0, this._g1, this._ptDist);
			return this._ptDist.Distance;
		}

		public Coordinate[] Coordinates
		{
			get
			{
				return this._ptDist.Coordinates;
			}
		}

		private void Compute(IGeometry g0, IGeometry g1)
		{
			this.ComputeOrientedDistance(g0, g1, this._ptDist);
			this.ComputeOrientedDistance(g1, g0, this._ptDist);
		}

		private void ComputeOrientedDistance(IGeometry discreteGeom, IGeometry geom, PointPairDistance ptDist)
		{
			DiscreteHausdorffDistance.MaxPointDistanceFilter maxPointDistanceFilter = new DiscreteHausdorffDistance.MaxPointDistanceFilter(geom);
			discreteGeom.Apply(maxPointDistanceFilter);
			ptDist.SetMaximum(maxPointDistanceFilter.MaxPointDistance);
			if (this._densifyFrac > 0.0)
			{
				DiscreteHausdorffDistance.MaxDensifiedByFractionDistanceFilter maxDensifiedByFractionDistanceFilter = new DiscreteHausdorffDistance.MaxDensifiedByFractionDistanceFilter(geom, this._densifyFrac);
				discreteGeom.Apply(maxDensifiedByFractionDistanceFilter);
				ptDist.SetMaximum(maxDensifiedByFractionDistanceFilter.MaxPointDistance);
			}
		}

		private readonly IGeometry _g0;

		private readonly IGeometry _g1;

		private readonly PointPairDistance _ptDist = new PointPairDistance();

		private double _densifyFrac;

		public class MaxPointDistanceFilter : ICoordinateFilter
		{
			public MaxPointDistanceFilter(IGeometry geom)
			{
				this._geom = geom;
			}

			public void Filter(Coordinate pt)
			{
				this._minPtDist.Initialize();
				DistanceToPoint.ComputeDistance(this._geom, pt, this._minPtDist);
				this._maxPtDist.SetMaximum(this._minPtDist);
			}

			public PointPairDistance MaxPointDistance
			{
				get
				{
					return this._maxPtDist;
				}
			}

			private readonly PointPairDistance _maxPtDist = new PointPairDistance();

			private readonly PointPairDistance _minPtDist = new PointPairDistance();

			private readonly IGeometry _geom;
		}

		public class MaxDensifiedByFractionDistanceFilter : ICoordinateSequenceFilter
		{
			public MaxDensifiedByFractionDistanceFilter(IGeometry geom, double fraction)
			{
				this._geom = geom;
				this._numSubSegs = (int)Math.Round(1.0 / fraction, MidpointRounding.ToEven);
			}

			public void Filter(ICoordinateSequence seq, int index)
			{
				if (index == 0)
				{
					return;
				}
				Coordinate coordinate = seq.GetCoordinate(index - 1);
				Coordinate coordinate2 = seq.GetCoordinate(index);
				double num = (coordinate2.X - coordinate.X) / (double)this._numSubSegs;
				double num2 = (coordinate2.Y - coordinate.Y) / (double)this._numSubSegs;
				for (int i = 0; i < this._numSubSegs; i++)
				{
					double x = coordinate.X + (double)i * num;
					double y = coordinate.Y + (double)i * num2;
					Coordinate pt = new Coordinate(x, y);
					this._minPtDist.Initialize();
					DistanceToPoint.ComputeDistance(this._geom, pt, this._minPtDist);
					this._maxPtDist.SetMaximum(this._minPtDist);
				}
			}

			public bool GeometryChanged
			{
				get
				{
					return false;
				}
			}

			public bool Done
			{
				get
				{
					return false;
				}
			}

			public PointPairDistance MaxPointDistance
			{
				get
				{
					return this._maxPtDist;
				}
			}

			private readonly PointPairDistance _maxPtDist = new PointPairDistance();

			private readonly PointPairDistance _minPtDist = new PointPairDistance();

			private readonly IGeometry _geom;

			private readonly int _numSubSegs;
		}
	}
}
